package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"strconv"
	"time"

	"github.com/andybalholm/brotli"
	"github.com/imroc/req"
)

func Danmu(userid, roomID int) {
	token, address := getDanmuInfo(userid, roomID)
	danmuTcp(userid, roomID, token, address)
}
func getDanmuInfo(userid, roomID int) (token, address string) {
	type IPList struct {
		Host string `json:"host"`
		Port int64  `json:"port"`
	}
	type Data struct {
		Token  string   `json:"token"`
		IPList []IPList `json:"ip_list"`
	}
	type DanmuInfoRes struct {
		Code    int64  `json:"code"`
		Message string `json:"message"`
		TTL     int64  `json:"ttl"`
		Data    Data   `json:"data"`
	}
	var body DanmuInfoRes
	uri := "https://api.live.bilibili.com/xlive/app-room/v1/index/getDanmuInfo?" + SignHD("access_key="+Btoml.BiliUser[userid].AccessToken+"&actionKey=appkey&appkey=dfca71928277209b&build=1250004&c_locale=zh_CN&channel=huawei&device=android&disable_rcmd=0&mobi_app=android_hd&platform=android&room_id="+strconv.Itoa(roomID)+"&s_locale=zh_CN&statistics=%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.25.0%22%2C%22abtest%22%3A%22%22%7D&ts="+StrTime()+"&version=1.25.0")
	r, err := req.C().R().SetResult(&body).Get(uri)
	if err != nil {
		printf("getDanmuInfo err: %v", err)
		return
	}
	if r.IsError() {
		printf("getDanmuInfo response err: %v", err)
		return
	}
	if body.Code != 0 {
		printf("getDanmuInfo err: %v", body.Message)
		return
	}
	token = body.Data.Token
	if len(body.Data.IPList) > 0 {
		address = fmt.Sprintf("%v:%v", body.Data.IPList[0].Host, body.Data.IPList[0].Port)
	}
	return
}

func danmuTcp(userid, roomID int, token, address string) {
	conn, err := net.Dial("tcp", address)
	if err != nil {
		printf("danmu Tcp Dail err: %v", err)
		return
	}
	firstMsg, err := Encode(append([]byte{0, 16, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0}, []byte(`{"clientver":"1.25.0.1250004","group":"","hwid":"`+randStr(31, 2, cookie2uid(Btoml.BiliUser[userid].Cookie))+`","key":"`+token+`","platform":"android","protover":3,"roomid":`+strconv.Itoa(roomID)+`,"uid":`+strconv.Itoa(cookie2uid(Btoml.BiliUser[userid].Cookie))+`}`)...))
	if err != nil {
		printf("danmu firstMsg Encode err: %v", err)
		return
	}
	_, err = conn.Write(firstMsg)
	if err != nil {
		printf("danmu firstMsg Write err: %v", err)
		return
	}
	go func() {
		for {
			time.Sleep(time.Second * 30)
			data, err := Encode([]byte{0, 16, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 123, 125})
			if err != nil {
				printf("danmu heart Encode err: %v", err)
				return
			}
			_, err = conn.Write(data)
			if err != nil {
				printf("danmu heart Write err: %v", err)
				return
			}
		}
	}()
	//read
	oldbyte := []byte{}
	for {
		buff := [2560]byte{}
		n, err := conn.Read(buff[:])
		if err != nil {
			printf("danmu read err: %v", err)
			return
		}
		buf := append(oldbyte, buff[:n]...)
		for {
			if len(buf) < 4 {
				oldbyte = buf
				break
			}
			alllength := int(buf[3]) + int(buf[2])*256 + int(buf[1])*256*256 + int(buf[0])*256*256*256
			if len(buf) < alllength {
				oldbyte = buf
				break
			} else {
				switch buf[11] {
				case 3:
					//心跳成功
				case 5:
					unzipMsg(buf[16:alllength])
				case 8:
					//printf("房间%v 弹幕连接成功    ", roomID)
				default:
				}
				if len(buf) == alllength {
					oldbyte = []byte{}
					break
				} else {
					buf = buf[alllength:]
				}
			}
		}
	}
}
func DoBrotliCompress(compressSrc []byte) ([]byte, error) {
	b := bytes.NewReader(compressSrc)
	var out bytes.Buffer
	r := brotli.NewReader(b)
	io.Copy(&out, r)
	return out.Bytes(), nil
}
func unzipMsg(msg []byte) []byte {
	buf, err := DoBrotliCompress(msg)
	if err != nil {
		cmdMsg(buf)
		return []byte{}
	}
	if len(buf) == 0 {
		cmdMsg(buf)
		return []byte{}
	}
	oldbyte := []byte{}
	for {
		if len(buf) < 4 {
			oldbyte = buf
			break
		}
		alllength := int(buf[3]) + int(buf[2])*256 + int(buf[1])*256*256 + int(buf[0])*256*256*256
		if len(buf) < alllength {
			//半包
			oldbyte = buf
			break
		} else {
			switch buf[11] {
			case 3:
				//心跳成功
			case 5:
				go cmdMsg(buf[16:alllength])
			case 8:
				//弹幕连接成功
			default:
			}
			if len(buf) == alllength {
				oldbyte = []byte{}
				break
			} else {
				buf = buf[alllength:]
			}
		}
	}
	if len(oldbyte) != 0 {
		//print("====unzipMsg有半包====")
	}
	return oldbyte
}
func cmdMsg(msg []byte) {
	//print(string(msg))
}

// Encode 将消息编码
func Encode(message []byte) ([]byte, error) {
	// 读取消息的长度，转换成int32类型（占4个字节）
	var length = int32(len(message))
	var pkg = new(bytes.Buffer)
	// 写入消息头
	err := binary.Write(pkg, binary.BigEndian, 4+length)
	if err != nil {
		return nil, err
	}
	// 写入消息实体
	err = binary.Write(pkg, binary.BigEndian, message)
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}
